/* rxrpc.c: description
 *
 * Copyright (C) 2007 Red Hat, Inc. All Rights Reserved.
 * Written by David Howells (dhowells@redhat.com)
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version
 * 2 of the License, or (at your option) any later version.
 */

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <poll.h>
#include <signal.h>
#include <errno.h>
#include <sys/socket.h>
#include <netinet/in.h>

#define VLGETENTRYBYID		503	/* AFS Get Cache Entry By ID operation ID */
#define VLGETENTRYBYNAME	504	/* AFS Get Cache Entry By Name operation ID */
#define VLPROBE			514	/* AFS Probe Volume Location Service operation ID */

struct sockaddr_rxrpc {
	sa_family_t	srx_family;	/* address family */
	unsigned short	srx_service;	/* service desired */
	unsigned short	transport_type;	/* type of transport socket (SOCK_DGRAM) */
	unsigned short	transport_len;	/* length of transport address */
	union {
		sa_family_t family;		/* transport address family */
		struct sockaddr_in sin;		/* IPv4 transport address */
		struct sockaddr_in6 sin6;	/* IPv6 transport address */
	} transport;
};

#define AF_RXRPC	33
#define PF_RXRPC	AF_RXRPC
#define SOL_RXRPC	272
#define RXRPC_USER_CALL_ID	1	/* User call ID specifier */
#define RXRPC_ABORT		2	/* Abort request / notification */
#define RXRPC_ACK		3	/* [Server] RPC op final ACK received */
#define RXRPC_RESPONSE		4	/* [Server] security response received */
#define RXRPC_NET_ERROR		5	/* network error received */
#define RXRPC_BUSY		6	/* server busy received */
#define RXRPC_LOCAL_ERROR	7	/* local error generated */
#define RXRPC_PREPARE_CALL_SLOT	8	/* Propose user call ID specifier for next call */
#define RXRPC_SECURITY_KEY		1	/* [clnt] set client security key */
#define RXRPC_SECURITY_KEYRING		2	/* [srvr] set ring of server security keys */
#define RXRPC_EXCLUSIVE_CONNECTION	3	/* [clnt] use exclusive RxRPC connection */
#define RXRPC_MIN_SECURITY_LEVEL	4	/* minimum security level */
#define RXRPC_SECURITY_PLAIN	0	/* plain secure-checksummed packets only */
#define RXRPC_SECURITY_AUTH	1	/* authenticated packets */
#define RXRPC_SECURITY_ENCRYPT	2	/* encrypted packets */

#define OSERROR(X, Y) do { if ((long)(X) == -1) { perror(Y); exit(1); } } while(0)

static const unsigned char local_addr[4] = { 0, 0, 0, 0 };
static const unsigned char remote_addr[4] = { 172, 16, 18, 91 };
//static const unsigned char remote_addr[4] = { 192, 168, 2, 129 };

#define RXRPC_ADD_CALLID(control, ctrllen, id)				\
do {									\
	struct cmsghdr *__cmsg;						\
	__cmsg = (void *)(control) + (ctrllen);				\
	__cmsg->cmsg_len	= CMSG_LEN(sizeof(unsigned long));	\
	__cmsg->cmsg_level	= SOL_RXRPC;				\
	__cmsg->cmsg_type	= RXRPC_USER_CALL_ID;			\
	*(unsigned long *)CMSG_DATA(__cmsg) = (id);			\
	(ctrllen) += __cmsg->cmsg_len;					\
									\
} while (0)

#define RXRPC_ADD_ABORT(control, ctrllen, abort_code)			\
do {									\
	struct cmsghdr *__cmsg;						\
	__cmsg = (void *)(control) + (ctrllen);				\
	__cmsg->cmsg_len	= CMSG_LEN(sizeof(unsigned int));	\
	__cmsg->cmsg_level	= SOL_RXRPC;				\
	__cmsg->cmsg_type	= RXRPC_ABORT;				\
	*(unsigned int *)CMSG_DATA(__cmsg) = (abort_code);		\
	(ctrllen) += __cmsg->cmsg_len;					\
									\
} while (0)

char cell[] = "afs@CAMBRIDGE.REDHAT.COM";
char vlname[] = "root.afs";

/*****************************************************************************/
/*
 * dump the control messages
 */
static void dump_cmsg(struct msghdr *msg)
{
	struct cmsghdr *cmsg;
	unsigned long user_id;
	unsigned char *p;
	int abort_code;
	int n;

	for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) {
		n = cmsg->cmsg_len - CMSG_ALIGN(sizeof(*cmsg));
		p = CMSG_DATA(cmsg);

		printf("CMSG: %zu: ", cmsg->cmsg_len);

		if (cmsg->cmsg_level == SOL_RXRPC) {
			switch (cmsg->cmsg_type) {
			case RXRPC_USER_CALL_ID:
				printf("RXRPC_USER_CALL_ID: ");
				if (n != sizeof(user_id))
					goto dump_data;
				memcpy(&user_id, p, sizeof(user_id));
				printf("%lx\n", user_id);
				continue;

			case RXRPC_ABORT:
				printf("RXRPC_ABORT: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%d\n", abort_code);
				continue;

			case RXRPC_ACK:
				printf("RXRPC_ACK");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_RESPONSE:
				printf("RXRPC_RESPONSE");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_NET_ERROR:
				printf("RXRPC_NET_ERROR: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%s\n", strerror(abort_code));
				continue;

			case RXRPC_BUSY:
				printf("RXRPC_BUSY");
				if (n != 0)
					goto dump_data_colon;
				goto print_nl;

			case RXRPC_LOCAL_ERROR:
				printf("RXRPC_LOCAL_ERROR: ");
				if (n != sizeof(abort_code))
					goto dump_data;
				memcpy(&abort_code, p, sizeof(abort_code));
				printf("%s\n", strerror(abort_code));
				continue;

			default:
				break;
			}
		}

		printf("l=%d t=%d", cmsg->cmsg_level, cmsg->cmsg_type);

	dump_data_colon:
		printf(": ");
	dump_data:
		printf("{");
		for (; n > 0; n--, p++)
			printf("%02x", *p);

	print_nl:
		printf("}\n");
	}
}

/*****************************************************************************/
/*
 *
 */
int main(int argc, char *argv[])
{
	struct sockaddr_rxrpc srx;
	unsigned char buffer[4096], control[4096], control2[4096];
	struct msghdr msg, msg2;
	struct iovec iov[3], iov2[3];
	unsigned int padding, param[2], sec;
	size_t ctrllen, ctrllen2;
	int client, ret, ioc, ioc2, loop;

	client = socket(AF_RXRPC, SOCK_DGRAM, PF_INET);
	OSERROR(client, "socket");

	/* set the security we want to use */
	sec = RXRPC_SECURITY_PLAIN;
	ret = setsockopt(client, SOL_RXRPC, RXRPC_MIN_SECURITY_LEVEL,
			 &sec, sizeof(sec));
	ret = setsockopt(client, SOL_RXRPC, RXRPC_SECURITY_KEY,
			 cell, strlen(cell));
	OSERROR(ret, "key");

	/* bind an address to the local endpoint */
	srx.srx_family = AF_RXRPC;
	srx.srx_service = 1; /* it's a client */
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(srx.transport.sin);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(7001);
	memcpy(&srx.transport.sin.sin_addr, &local_addr, 4);

	ret = bind(client, (struct sockaddr *) &srx, sizeof(srx));
	OSERROR(client, "bind");

	//system("netstat -ua");

	/* connect to the remote server */
	srx.srx_family = AF_RXRPC;
	srx.srx_service = 52;
	srx.transport_type = SOCK_DGRAM;
	srx.transport_len = sizeof(srx.transport.sin);
	srx.transport.sin.sin_family = AF_INET;
	srx.transport.sin.sin_port = htons(7003);
	memcpy(&srx.transport.sin.sin_addr, &remote_addr, 4);

#if 0
	ret = connect(client, (struct sockaddr *) &srx, sizeof(srx));
	OSERROR(ret, "connect");
#endif

	/* request an operation */
	ctrllen = 0;
	RXRPC_ADD_CALLID(control, ctrllen, 0x12345);

	ctrllen2 = 0;
	RXRPC_ADD_CALLID(control2, ctrllen2, 0x6543);

#if 1
	iov[0].iov_len = sizeof(param);
	iov[0].iov_base = param;

	iov[1].iov_len = sizeof(vlname) - 1;
	iov[1].iov_base = vlname;

	padding = 0;
	iov[2].iov_len = 4 - (iov[1].iov_len & 3);
	iov[2].iov_base = &padding;

	ioc = (iov[2].iov_len > 0) ? 3 : 2;

	param[0] = htonl(VLGETENTRYBYNAME);
	param[1] = htonl(iov[1].iov_len);


	iov2[0].iov_len = sizeof(param);
	iov2[0].iov_base = param;

	iov2[1].iov_len = sizeof(vlname) - 1;
	iov2[1].iov_base = vlname;

	iov2[2].iov_len = 4 - (iov2[1].iov_len & 3);
	iov2[2].iov_base = &padding;

	ioc2 = (iov2[2].iov_len > 0) ? 3 : 2;

#else
	iov[0].iov_len = sizeof(param);
	iov[0].iov_base = param;

	char filename[] = "/usr/share/doc/lsof-4.77/00FAQ";
	//char filename[] = "/usr/share/doc/lsof-4.77/00MANIFEST";

	iov[1].iov_len = sizeof(filename) - 1;
	iov[1].iov_base = filename;

	padding = 0;
	iov[2].iov_len = 4 - (iov[1].iov_len & 3);
	iov[2].iov_base = &padding;

	ioc = (iov[2].iov_len > 0) ? 3 : 2;

	printf("pad: %d\n", ioc);

	param[0] = htonl(1);  // BULK_FetchFile
	param[1] = htonl(1);
	param[2] = htonl(iov[1].iov_len);
	
#endif

	msg.msg_name		= (struct sockaddr *) &srx;
	msg.msg_namelen		= sizeof(srx);
	msg.msg_iov		= iov;
	msg.msg_iovlen		= ioc;
	msg.msg_control		= control;
	msg.msg_controllen	= ctrllen;
	msg.msg_flags		= 0;

	msg2.msg_name		= NULL;
	msg2.msg_namelen	= 0;
	msg2.msg_iov		= iov2;
	msg2.msg_iovlen		= ioc2;
	msg2.msg_control	= control2;
	msg2.msg_controllen	= ctrllen2;
	msg2.msg_flags		= 0;

	ret = sendmsg(client, &msg, 0);
	if (ret == -1 && (errno == ENOANO || errno == ECONNABORTED))
		perror("sendmsg/data");
	else
		OSERROR(ret, "sendmsg/data");

//	ret = sendmsg(client, &msg2, 0);
//	if (ret == -1 && (errno == ENOANO || errno == ECONNABORTED))
//		perror("sendmsg/data");
//	else
//		OSERROR(ret, "sendmsg/data");

//	if (ret != -1)
//		sleep(1);

	/* abort the operation */
#if 0
	ctrllen = 0;

	RXRPC_ADD_CALLID(control, ctrllen, 0x12345);
	RXRPC_ADD_ABORT(control, ctrllen, 0x6789);

	msg.msg_name		= NULL;
	msg.msg_namelen		= 0;
	msg.msg_iov		= NULL;
	msg.msg_iovlen		= 0;
	msg.msg_control		= control;
	msg.msg_controllen	= ctrllen;
	msg.msg_flags		= 0;

	ret = sendmsg(client, &msg, 0);
	OSERROR(ret, "sendmsg/abort");
#endif

	/* wait for a reply */
	for (;;) {
		iov[0].iov_base = buffer;
		iov[0].iov_len = sizeof(buffer);

		msg.msg_name = NULL;
		msg.msg_namelen = 0;
		msg.msg_iov = iov;
		msg.msg_iovlen = 1;
		msg.msg_control = control;
		msg.msg_controllen = sizeof(control);
		msg.msg_flags = 0;

		ret = recvmsg(client, &msg, 0);
		OSERROR(ret, "recvmsg");

		printf("RECV: %d [fl:%d]\n", ret, msg.msg_flags);
		printf("CMSG: %zu\n", msg.msg_controllen);
		printf("IOV: %zu [0]=%zu\n", msg.msg_iovlen, iov[0].iov_len);
		dump_cmsg(&msg);

#if 1
		for (loop = 0; loop < ret; loop++) {
			printf("%02x", buffer[loop]);
			if (loop % 40 == 39)
				putchar('\n');
			else if (loop % 4 == 3)
				putchar(' ');
		}
		putchar('\n');
#endif
	}

	/* done */
	ret = close(client);
	OSERROR(ret, "close");

	return 0;
}
